use chrono::DateTime;
use chrono::NaiveDate;
use chrono::Utc;
use fudata::db;
use fudata::model::kline::Kline;
use fudata::model::kline::Klt;
use fudata::round2;
use log::debug;
use anyhow::Result;

use crate::todo::MAX_KLINE_DAYS;

/// A struct to represent the period of a flat_sideways movement.
#[derive(Debug)]
pub struct SidewaysPeriod {
    security_id: String,
    start: NaiveDate,
    end: NaiveDate,
    days: isize, // Duration in days
    lowest: f64,
    highest: f64,
    threshold: f64, // The threshold used for this period
}

/// A struct to hold the results for each stock.
#[derive(Debug)]
struct StockSidewaysInfo {
    stock_id: String,
    flat_sideways_periods: Vec<SidewaysPeriod>,
}

/// Computes the duration in units of Klt.
fn compute_duration_in_days(start: &DateTime<Utc>, end: &DateTime<Utc>) -> isize {
    ((end.timestamp() - start.timestamp()) as isize) / (24 * 60 * 60)
}

///
/// 横盘查找
/// A function to find all flat_sideways periods for a given stock and a single threshold.
///
/// threshold: The threshold to use for finding flat_sideways periods
///
/// (sma_v5 - sma_v21).abs()/sma_v21;
///
fn find_flat_sideways_periods_for_threshold(
    security_id: &str,
    klines: &[Kline],
    threshold: f64
) -> Result<Vec<SidewaysPeriod>> {
    let mut flat_sideways_periods = Vec::new();
    let mut start_timestamp = None;
    let mut end_timestamp = None;
    let mut current_high = f64::MIN;
    let mut current_low = f64::MAX;

    for kline in klines {
        // Update the current high and low.
        if kline.high > current_high {
            current_high = kline.high;
        }
        if kline.low < current_low {
            current_low = kline.low;
        }

        // Check if the current high and low are within the threshold.
        if (current_high - current_low).abs() / current_low <= threshold {
            if start_timestamp.is_none() {
                start_timestamp = Some(kline.klts); // Convert to NaiveDate
            }
            end_timestamp = Some(kline.klts); // Convert to NaiveDate
        } else {
            if let (Some(start), Some(end)) = (start_timestamp, end_timestamp) {
                // Calculate the duration of the current sideways period.
                let duration_in_days = compute_duration_in_days(&start, &end);

                if duration_in_days > 0 {
                    flat_sideways_periods.push(SidewaysPeriod {
                        security_id: security_id.to_string(),
                        start: start.date_naive(),
                        end: end.date_naive(),
                        days: duration_in_days,
                        lowest: current_low,
                        highest: current_high,
                        threshold,
                    });
                }
            }
            // Reset the current high and low.
            current_high = kline.high;
            current_low = kline.low;
            start_timestamp = None;
            end_timestamp = None;
        }
    }

    // Add the last flat_sideways period if it's still ongoing at the end of the loop.
    if let (Some(start), Some(end)) = (start_timestamp, end_timestamp) {
        let duration_in_days = compute_duration_in_days(&start, &end);

        if duration_in_days > 0 {
            flat_sideways_periods.push(SidewaysPeriod {
                security_id: security_id.to_string(),
                start: start.date_naive(),
                end: end.date_naive(),
                days: duration_in_days,
                lowest: current_low,
                highest: current_high,
                threshold,
            });
        }
    }
    Ok(flat_sideways_periods)
}

/// A function to find all flat_sideways periods for a given stock and a range of thresholds.
async fn find_all_flat_sideways_periods(
    security_id: &str,
    min_threshold: f64,
    max_threshold: f64,
    step: f64
) -> Result<Vec<SidewaysPeriod>> {
    let mut all_flat_sideways_periods = Vec::new();

    let step_size = (max_threshold - min_threshold) / ((max_threshold - min_threshold) / step);
    let num_steps = ((max_threshold - min_threshold) / step) as usize;

    let klines = db::kline::select(
        security_id,
        None,
        Some(&Klt::DAY),
        Some("klts ASC"),
        0,
        MAX_KLINE_DAYS as u16
    ).await?;

    println!("find_all_flat_sideways_periods: {}", security_id);
    for i in 0..=num_steps {
        let threshold = round2(min_threshold + (i as f64) * step_size);
        let flat_sideways_periods = find_flat_sideways_periods_for_threshold(
            security_id,
            &klines,
            threshold
        )?;
        all_flat_sideways_periods.extend(flat_sideways_periods);
    }

    Ok(all_flat_sideways_periods)
}

#[cfg(test)]
mod test {
    use std::vec;

    use fudata::db;
    use fudata::model::security::{ self, Security };
    use fudata::model::security::Market;
    use surrealdb::sql::Thing;

    use crate::qtrade::strategy::util::flat_sideways::find_all_flat_sideways_periods;

    #[tokio::test]
    async fn test_find_all_flat_sideways_periods() {
        let _ = db::init().await.unwrap();

        let securities = db::security
            ::select(
                &Some(Market::SZ),
                Some(security::SecurityType::STK),
                None,
                Some("id ASC"),
                0,
                100 //MAX_STK_SECURITY
            ).await
            .unwrap();
        let securities = vec![Security {
            id: Some(Thing::from(("security", "SH.STK.600171"))),
            ..Default::default()
        }];

        for security in securities {
            let security_id = security.id.unwrap().id.to_raw(); // "SZ.STK.300675";
            let rs = find_all_flat_sideways_periods(&security_id, 0.1, 0.3, 0.01).await;
            if let Ok(mut flat_sideways_periods) = rs {
                flat_sideways_periods.sort_by(|a, b| b.end.cmp(&a.end));
                for (idx, sp) in flat_sideways_periods.iter().enumerate() {
                    if idx < 360 && sp.days > 10 {
                        println!("[{idx}]{:?}", sp);
                    }
                }
            }
        }
    }
}
